using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

namespace DotNetCommon.Expressions.Base
{
    internal class BaseVisit
    {
        /// <summary>
        /// 生成当前节点的字符串描述
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        protected virtual string GenerateFullMarkString(ExpressionNode node) => throw new NotImplementedException();
        /// <summary>
        /// 准备树形结构
        /// </summary>
        /// <param name="node"></param>
        /// <exception cref="NotImplementedException"></exception>
        protected virtual void Prepare(ExpressionNode node) => throw new NotImplementedException();
        /// <summary>
        /// 重新生成当前节点(当子节点有简化动作时,父节点要冒泡一样一级级跟着Rebuild)
        /// </summary>
        /// <param name="node"></param>
        /// <remarks>注意：简化后不能更改当前节点的类型,比如: [bool? ok;p=>ok??p.Id>1;] 整体是book,Left是bool?, 简化后虽然使用了[ok]的值, 但整体类型应仍然是[bool]</remarks>
        /// <returns></returns>
        protected virtual Expression Rebuild(ExpressionNode node) => node.Expression;

        /// <summary>
        /// 简化表达式树入口
        /// </summary>
        /// <param name="node"></param>
        /// <param name="visit"></param>
        public void Reduce(ExpressionNode node, Action<ExpressionNode> visit)
        {
            Prepare(node);
            node.Children.ForEach(x => visit(x));
            node.FullMarkString = GenerateFullMarkString(node);
            SimpleReduce(node);
            Reduce2(node);
        }
        /// <summary>
        /// 在SimpleReduce基础上再进行一次简化,处理短路的运算符,比如: lambda[p=>true||p.IsWrite] 会简化为 Constant[true]
        /// </summary>
        /// <param name="node"></param>
        protected virtual void Reduce2(ExpressionNode node) { }

        /// <summary>
        /// 通用节点简化方法,简化条件:
        /// <list type="bullet">
        /// <item>当前节点必须有两个或以上的子节点;</item>
        /// <item>子节点中必须同时存在含参数的节点和不含参数的节点;</item>
        /// </list>
        /// 这样, 不含参数的子节点将被简化, 如果所有子节点都不含参数, 那说明简化的时机还没到, 延迟到父节点进行判断
        /// </summary>
        /// <param name="node"></param>
        private void SimpleReduce(ExpressionNode node)
        {
            //判断是否能简化
            if (node.Children.Count > 1)
            {
                var canReduce = false;
                for (int i = 0; i < node.Children.Count - 1; i++)
                {
                    if (node.Children[i].HasParameter ^ node.Children[i + 1].HasParameter)
                    {
                        //不相同 要进行简化
                        canReduce = true;
                        break;
                    }
                }
                if (canReduce)
                {
                    for (int i = 0; i < node.Children.Count; i++)
                    {
                        var child = node.Children[i];
                        if (!child.HasParameter && child.Expression != null)
                        {
                            if (child.NodeType != ExpressionType.Constant)
                            {
                                //进行简化
                                var res = child.Reduce();
                                var contant = Expression.Constant(res, child.Expression.Type);
                                node.Children[i] = new ExpressionNode
                                {
                                    Parent = node,
                                    Expression = contant,
                                    _updateRequest = true
                                };
                            }
                        }
                    }
                }
            }
            if (node.NeedUpdate)
            {
                node.Expression = Rebuild(node);
            }
        }

        /// <summary>
        /// 获取表达式树访问的参数属性名称
        /// </summary>
        /// <param name="node"></param>
        /// <param name="visit"></param>
        /// <param name="parameters"></param>
        /// <param name="names"></param>
        public virtual void GetAccessName(ExpressionNode node, Action<ExpressionNode> visit, List<ParameterExpression> parameters, List<List<string>> names)
        {
            Prepare(node);
            node.Children.ForEach(x => visit(x));
            if (node.NodeType == ExpressionType.MemberAccess)
            {
                var member = node.Expression as MemberExpression;
                if (member.Expression.NodeType == ExpressionType.Parameter)
                {
                    var index = parameters.IndexOf(member.Expression as ParameterExpression);
                    if (index >= 0)
                    {
                        names[index].Add(member.Member.Name);
                    }
                }
            }
        }
    }
}
